(setq helm-ff-preferred-shell-mode 'shell-mode)

;;; Shared global history.
(defvar ambrevar/shell-history-global-ring nil
  "The history ring shared across shell sessions.")

(defun ambrevar/shell-use-global-history ()
  "Make shell history shared across different sessions."
  (unless ambrevar/shell-history-global-ring
    (when comint-input-ring-file-name
      (comint-read-input-ring))
    (setq ambrevar/shell-history-global-ring (or comint-input-ring (make-ring comint-input-ring-size))))
  (setq comint-input-ring ambrevar/shell-history-global-ring))

(defun ambrevar/shell-history-remove-duplicates ()
  (require 'functions) ; For `ambrevar/ring-delete-first-item-duplicates'.
  (ambrevar/ring-delete-first-item-duplicates comint-input-ring))

(defvar ambrevar/comint-input-history-ignore (concat "^" (regexp-opt '("#" " " "cd ")))
  "`comint-input-history-ignore' can only be customized globally
because `comint-read-input-ring' uses a temp buffer.")

(defun ambrevar/shell-remove-ignored-inputs-from-ring ()
  "Discard last command from history if it matches
`ambrevar/comint-input-history-ignore'."
  (unless (ring-empty-p comint-input-ring)
    (when (string-match ambrevar/comint-input-history-ignore
                        (ring-ref comint-input-ring 0))
      (ring-remove comint-input-ring 0))))

(defun ambrevar/shell-sync-input-ring (_)
  (ambrevar/shell-history-remove-duplicates)
  (ambrevar/shell-remove-ignored-inputs-from-ring)
  (comint-write-input-ring))

(defun ambrevar/shell-setup ()
  (setq comint-input-ring-file-name
        (expand-file-name "shell-history" user-emacs-directory))
  (ambrevar/shell-use-global-history)

  ;; Write history on every command, not just on exit.
  (add-hook 'comint-input-filter-functions 'ambrevar/shell-sync-input-ring nil t)

  ;; Only ending with '#' or '$' but seems slower:
  ;; (setq comint-prompt-regexp "^[^#$]*
  ;; [^#$]*[#$>] +")
  (setq comint-prompt-regexp "^[^#$%>]*
\[^#$%>]*[#$%>] +"))

(add-hook 'shell-mode-hook 'ambrevar/shell-setup)

(defun ambrevar/shell-prompt-begin-position ()
  ;; We need this convoluted function because `looking-at-p' does not work on
  ;; multiline regexps _and_ `re-search-backward' skips the current line.
  (save-excursion
    (let ((old-point (point)))
      (max
       (save-excursion
         ;; Right result if not on prompt.
         (call-interactively #'comint-previous-prompt)
         (re-search-backward comint-prompt-regexp)
         (point))
       (save-excursion
         ;; Right result if on first char after prompt.
         (re-search-backward comint-prompt-regexp)
         (point))
       (save-excursion
         ;; Right result if on prompt.
         (call-interactively #'comint-next-prompt)
         (re-search-backward comint-prompt-regexp)
         (if (<= (point) old-point)
             (point)
           (point-min)))))))

(defun ambrevar/shell-prompt-end-position ()
  (save-excursion
    (goto-char (ambrevar/shell-prompt-begin-position))
    (call-interactively #'comint-next-prompt)
    (point)))

(defun ambrevar/shell-prompt ()
  (buffer-substring-no-properties
   (ambrevar/shell-prompt-begin-position)
   (ambrevar/shell-prompt-end-position)))

(defun ambrevar/shell-propertize-prompt ()        ; Inspired by `shx--propertize-prompt'.
  "Add a mouseover timestamp to the last prompt."
  (let ((inhibit-read-only t)
        (inhibit-field-text-motion t))
    (add-text-properties
     (save-excursion
       (re-search-backward comint-prompt-regexp nil :noerror)
       (point))
     (process-mark (get-buffer-process (current-buffer)))
     `(help-echo ,(format-time-string "%F %T")))))

(defun ambrevar/shell-send-input ()
  "Send or parse the input currently written at the prompt.
In normal circumstances this input is additionally filtered by
`shx-filter-input' via `comint-mode'."
  (interactive)
  (ambrevar/shell-propertize-prompt)
  (comint-send-input))
(define-key shell-mode-map (kbd "<return>") 'ambrevar/shell-send-input)

(defun ambrevar/shell-command-duration ()
  "Return duration of command at point in a `shell' buffer."
  (interactive)
  (let ((begin (ignore-errors (parse-time-string (get-text-property
                                                  (ambrevar/shell-prompt-begin-position)
                                                  'help-echo))))
        (end (parse-time-string (save-excursion
                                  (goto-char (ambrevar/shell-prompt-end-position))
                                  (call-interactively #'comint-next-prompt)
                                  (ambrevar/shell-prompt)))))
    (if begin
        (message "Command took %.0f seconds."
                 (- (float-time (apply 'encode-time end))
                    (float-time (apply 'encode-time begin))))
      (message "No timestamp."))))

(defun ambrevar/shell-narrow-to-prompt ()
  "Narrow buffer to prompt at point."
  (interactive)
  (let ((begin (ambrevar/shell-prompt-begin-position)))
    (narrow-to-region
     begin
     (save-excursion
       (goto-char (ambrevar/shell-prompt-end-position))
       (call-interactively #'comint-next-prompt)
       (if (= begin
              (ambrevar/shell-prompt-begin-position))
           (point-max)
         (ambrevar/shell-prompt-begin-position))))))
(define-key shell-mode-map (kbd "C-x n d") 'ambrevar/shell-narrow-to-prompt)

(define-key shell-mode-map (kbd "C-x M-O") 'comint-truncate-buffer)

(provide 'init-shell)
